
#include <string.h>
#include "board.h"
#include "ev_flash.h"
#include "ev_utils.h"

int ev_ota_erase(firmware_type_t type)
{
    uint32_t addr = 0;
    uint32_t size = 0;

    switch (type) {
        case FIRMWARE_TYPE_BOOT:
            addr = BOOT_BASE;
            size = BOOT_SIZE;
            break;
        case FIRMWARE_TYPE_APP1:
            addr = APP1_BASE;
            size = APP1_SIZE;
            break;
        case FIRMWARE_TYPE_APP2:
            addr = APP2_BASE;
            size = APP2_SIZE;
            break;
        default:
            return -1;
    }

    if (ota_flash_erase(addr, size) != 0) {
        return -2;
    }
    return 0;
}

int ev_ota_write(firmware_type_t type, uint32_t offset, const uint8_t* data, uint32_t len)
{
    uint32_t addr = 0;
    uint32_t size = 0;

    switch (type) {
        case FIRMWARE_TYPE_BOOT:
            addr = BOOT_BASE;
            size = BOOT_SIZE;
            break;
        case FIRMWARE_TYPE_APP1:
            addr = APP1_BASE;
            size = APP1_SIZE;
            break;
        case FIRMWARE_TYPE_APP2:
            addr = APP2_BASE;
            size = APP2_SIZE;
            break;
        default:
            return -1;
    }

    if ((offset + len) > size) {
        return -2;
    }

    if (ota_flash_write(addr + offset, data, len) != 0) {
        return -3;
    }
    return 0;

}

int ev_ota_read(firmware_type_t type, uint32_t offset, uint8_t* data, uint32_t len)
{
    uint32_t addr = 0;
    uint32_t size = 0;
    const uint8_t* src;

    switch (type) {
        case FIRMWARE_TYPE_BOOT:
            addr = BOOT_BASE;
            size = BOOT_SIZE;
            break;
        case FIRMWARE_TYPE_APP1:
            addr = APP1_BASE;
            size = APP1_SIZE;
            break;
        case FIRMWARE_TYPE_APP2:
            addr = APP2_BASE;
            size = APP2_SIZE;
            break;
        default:
            return -1;
    }

    if ((offset + len) > size) {
        return -2;
    }

    src = (const uint8_t *)(addr + offset);
    memcpy(data, src, len);
    return 0;
}

static int _find_firm_info(uint32_t addr, uint32_t size, uint32_t *result)
{
    for (uint32_t offset = 0; (offset + sizeof(firm_info_t)) < size; offset += 4) {
        firm_info_t* info = (firm_info_t *)(addr + offset);
        if (info->magic == FIMRWARE_MAGIC) {
            if (ev_crc32((uint8_t *)info, sizeof(firm_info_t) - 4, 0xffffffff) == info->crc32) {
                if (result) {
                    *result = (uint32_t)info;
                }
                return 0;
            }
        }
    }
    return -1;
}

int ev_ota_set_dirty(firmware_type_t type)
{
    uint32_t addr = 0;
    uint32_t size = 0;
    switch (type) {
        case FIRMWARE_TYPE_BOOT:
            addr = BOOT_BASE;
            size = BOOT_SIZE;
            break;
        case FIRMWARE_TYPE_APP1:
            addr = APP1_BASE;
            size = APP1_SIZE;
            break;
        case FIRMWARE_TYPE_APP2:
            addr = APP2_BASE;
            size = APP2_SIZE;
            break;
        default:
            return -1;
    }
    firm_info_t *info;
    if (_find_firm_info(addr, size, (uint32_t *)&info) != 0) {
        return -2;
    }
    uint32_t new_dirty = 0x58652320;
    if (ota_flash_write((uint32_t)&info->dirty, (uint8_t *)&new_dirty, 4) != 0) {
        return -3;
    }
    return 0;
}

int ev_ota_check(firmware_type_t type)
{
    uint32_t addr = 0;
    uint32_t size = 0;
    switch (type) {
        case FIRMWARE_TYPE_BOOT:
            addr = BOOT_BASE;
            size = BOOT_SIZE;
            break;
        case FIRMWARE_TYPE_APP1:
            addr = APP1_BASE;
            size = APP1_SIZE;
            break;
        case FIRMWARE_TYPE_APP2:
            addr = APP2_BASE;
            size = APP2_SIZE;
            break;
        default:
            return -1;
    }
    uint32_t result = 0;
    if (_find_firm_info(addr, size, &result) != 0) {
        return -2;
    }
    const firm_info_t *info = (firm_info_t *)result;
    if (info->dirty != 0xffffffff) {
        return -3;
    }
    if (info->size != result - addr) {
        return -4;
    }
    uint8_t hash[32];
    ev_sha256((const uint8_t *)addr, info->size, hash);
    if (memcmp(hash, info->sha256, 32) != 0) {
        return -5;
    }
    return 0;
}
